using Math;

namespace Noise
{
    public static class PerlinDerivative
    {
        private static float Fade(float t)
        {
            return t*t*t*(t*(t*6-15)+10);
        }

        private static float Deriv(float x)
        {
            return x*x*(x*(30*x-60)+30);
        }

        public static float Noise2(float x, float y, out float dx, out float dy)
        {
            int i0, j0, i1, j1;
            float u, v;

            // Find unit square that contains point and find relative x,y of point in square
            Maths.GetIntegerAndFractional(x, out i0, out x);
            Maths.GetIntegerAndFractional(y, out j0, out y);

            // Compute fade curves for each of x,y
            u = Fade(x);
            v = Fade(y);

            // Compute derivative curves for each of x,y
            dx = Deriv(x);
            dy = Deriv(y);

            // Wrap integer coords at 255
            i1 = (i0+1)&255;
            j1 = (j0+1)&255;
            i0 &= 255;
            j0 &= 255;

            // Hash coordinates and get the gradient at the 4 square corners
            float g00 = Gradient.Dot2(Permutation.Perm2(i0, j0), x, y);
            float g10 = Gradient.Dot2(Permutation.Perm2(i1, j0), x-1, y);
            float g01 = Gradient.Dot2(Permutation.Perm2(i0, j1), x, y-1);
            float g11 = Gradient.Dot2(Permutation.Perm2(i1, j1), x-1, y-1);

            // Blend the results along x
            float nx0 = Interpolate.Lerp(u, g00, g10);
            float nx1 = Interpolate.Lerp(u, g01, g11);

            // Blend the results along y
            float nxy = Interpolate.Lerp(v, nx0, nx1);

            // Compute derivatives
            dx = dx*((g10-g00)+(g00-g10-g01+g11)*v);
            dy = dy*((g01-g00)+(g00-g10-g01+g11)*u);

            // Return the result, roughly normalised to the range [-1, 1]
            return nxy;
        }

        public static float Noise2(float x, float y, int octaves, float lacunarity, float gain)
        {
            float sum = 0.0f;
            float freq = 1.0f;
            float amp = 1.0f;
            float dsumx = 0.0f, dsumy = 0.0f;

            // Sum noise octaves
            for (int i=0; i<octaves; i++)
            {
                float dx, dy;
                float n = Noise2(x*freq, y*freq, out dx, out dy);
                dsumx += dx;
                dsumy += dy;
                sum += amp*n/(1.0f+(dsumx*dsumx+dsumy*dsumy));
                freq *= lacunarity;
                amp *= gain;
            }

            // Return the result
            return sum;
        }
    }
}